成為 UGUI 的排版大師

您所在的位置:网站首页 unity 修改recttransform 成為 UGUI 的排版大師

成為 UGUI 的排版大師

2024-04-23 14:16| 来源: 网络整理| 查看: 265

想要成為 Unity Canvas UI (UGUI) 的排版大師,那麼我們就一定要充分了解 UGUI 最基礎的排版類別 RectTransform ,它屬於 Transform 的子類別,用於描述元件如何擺放在二維介面中,既然是在二維介面中元件的 Transform,那麼叫做 Rect 的 Transform 也是很合理的吧!關於 RectTransform 的官方技術文件我們可以在 Unity - Manual: Rect Transform 找得到。

在編輯器中快速設定 RectTransform

快速控制器的基本操作我們就不在這裡贅述,基本上都可以在 Unity 網站裡找到很好的教學文件與影片。

RectTransform 的控制精髓:錨點們 Anchor Points

在透過快速設定器修改 RectTransform 的過程中,你會發現右上區域會隨著不同的配置出現不同的屬性設定如下圖:

Pos X 與 Left、Pos Y 與 Top、Width 與 Right、Height 與 Bottom 這四對屬性個別不會同時出現,有 Pos X 就沒有 Left,有 Width 就沒有 Right,那麼他們出現的規則是什麼呢?

簡單來說其實就是:

「當兩個錨點的某一維度值相等時,該維度的尺寸則是固定的(跟 Parent 尺寸無關),反之該維度的尺寸則是相對於 Parent 的尺寸而變化。」

其實全部都取決於控制 RectTransform 型態最重要的屬性「最大與最小錨點們(Min / Max Anchors)」,而快速設定器其實也只是在幫你快速的調整這兩個錨點的值,所以只要了解這兩個設定值關係與行為,其實你已經完全掌握了 RectTransform ,而依照上述邏輯,透過兩個錨點所產生出的配置型態總共有四種:

A. 當兩錨點 x, y 維度的值都相等時。 B. 當兩錨點 x 維度的值不相等、y 維度值相等時。 C. 當兩錨點 x 維度的值相等、y 維度值不相等時。 D. 當兩錨點 x, y 維度的值都不相等時。

A. 當兩錨點 x, y 維度的值都相等時:

當兩錨點 x, y 值都相等時,代表此物件的寬高尺寸都是固定值,所以我們會透過 PosX、PosY、Width 以及 Height 來定義此物件的顯示方式,PosX 與 PosY 則分別表示錨點到物件 Pivot 點的位移,而此物件的實際顯示區域則會受到 Pivot 的 x, y 值設定所影響。

B. 當兩錨點 x 維度的值不相等、y 維度值相等時:

當兩錨點 x 維度的值不相等、y 維度值相等時,代表 x 維度的尺寸會受到 Parent 的尺寸影響,在 x 維度上則是使用間距(Padding)的概念來排版,所以會用到 Left、PosY、Right 以及 Height,實際的 Width 是由 Left 與 Right 來控制。

C. 當兩錨點 x 維度的值相等、y 維度值不相等時:

當兩錨點 x 維度的值相等、y 維度值不相等時,代表 y 維度的尺寸會受到 Parent 的尺寸影響,在 y 維度上則是使用間距(Padding)的概念來排版,所以會用到 PosX、Top、Width 以及 Bottom,實際的 Height 是由 Top 與 Bottom 來控制。

D. 當兩錨點 x, y 維度的值都不相等時:

當兩錨點 x, y 維度的值都不相等時,代表物件的寬高尺寸都會受到 Parent 的影響,完全是使用四個方向的間距來定義此物件的顯示區域 Left、Top、Right 以及 Bottom。

用程式碼成為 RectTransform 的操控大師

當我們充分瞭解了 RectTransform 的運作原理後,我們接著就可以透過撰寫程式在程式運行中輕鬆控制排版的行為,由於 RectTransform 是 Transform 的子類別,所以直接透過轉型就可以拿到:

var rectTransform = this.transform as RectTransform

而所有的 UI Element 也都可以直接透過屬性 rectTranform 獲得:

public Text SomeText; void Awake () { var rectTransform = SomeText.rectTransform; } RectTransform 的屬性

在使用程式碼控制 RectTranform 之前,我們必須清楚了解各屬性實際代表的意義,才不會在執行時出現非預期的排版狀況,其中最常見的錯誤就是把 sizeDelta 誤認為在設定真正的 size,或是把父類別的 localPosition 當作 anchoredPosition 來用,下圖為所有參數的幾何關係圖:

sizeDelta 根據文件定義是:The size of this RectTransform relative to the distances between the anchors.

所以 RectTransform 真正的計算出的 rect.width 會等於 (anchorMax.x - anchorMin.x) * Parent.rect.width + sizeDelta.x

同理

rect.height 會等於 (anchorMax.y - anchorMin.y) * Parent.rect.height + sizeDelta.y

意思就是 sizeDelta 個別維度的值是跟兩錨點個別維度的差值相關,所以只有當兩錨點某的維度的值相等的時候,sizeDelta 在此維度的值才會剛好等於最後顯示的 size 大小。

而 offsetMin 則代表 Min 錨點到物體顯示區域左下角點的向量(注意正反方向);offsetMax 則代表 Max 錨點到物體顯示區域右上角點的向量,這就是為什麼 offsetMax 的值跟編輯器中 Top、Right 值剛好正負相反的原因。

實作對齊 (Alignment)排版功能

為了方便使用對齊功能,我們將各種對齊行為透過 FDRectAlignment 列舉(enum)來表達:

public enum FDRectAlignment { TopLeft = 0, Top, TopRight, Left, Center, Right, BottomLeft, Bottom, BottomRight }

接著我們就來擴充 RectTransform 類別對齊的方法:

public static class FDRectTransform { static void SetAlignment (this RectTransform rect, Vector2 value) { rect.anchorMax = value; rect.anchorMin = value; } public static void TopLeft (this RectTransform rect) { rect.SetAlignment (Vector2.up); } public static void Top (this RectTransform rect) { rect.SetAlignment (new Vector2 (0.5f, 1f)); } public static void TopRight (this RectTransform rect) { rect.SetAlignment (Vector2.one); } public static void Left (this RectTransform rect) { rect.SetAlignment (new Vector2 (0f, 0.5f)); } public static void Center (this RectTransform rect) { rect.SetAlignment (new Vector2 (0.5f, 0.5f)); } public static void Right (this RectTransform rect) { rect.SetAlignment (new Vector2 (1f, 0.5f)); } public static void BottomLeft (this RectTransform rect) { rect.SetAlignment (Vector2.zero); } public static void Bottom (this RectTransform rect) { rect.SetAlignment (new Vector2 (0.5f, 0f)); } public static void BottomRight (this RectTransform rect) { rect.SetAlignment (Vector2.right); } public static void Align (this RectTransform rect) { switch (alignment) { case FDRectAlignment.TopLeft: rect.TopLeft (); break; case FDRectAlignment.Top: rect.Top (); break; case FDRectAlignment.TopRight: rect.TopRight (); break; case FDRectAlignment.Left: rect.Left (); break; case FDRectAlignment.Center: rect.Center (); break; case FDRectAlignment.Right: rect.Right (); break; case FDRectAlignment.BottomLeft: rect.BottomLeft (); break; case FDRectAlignment.Bottom: rect.Bottom (); break; case FDRectAlignment.BottomRight: rect.BottomRight (); break; } } }

完成後我們就可以在其他地方輕鬆動態使用對齊:

public class AlignTest : MonoBehaviour { public RectTransform SomeRect; void AlignmentRect () { SomeRect.BottomRight (); /// or use enum SomeRect.Align (FDRectAlignment.BottomRight); } }

但這樣的方式只有針對物體的 Pivot 做對齊,為了達到針對物體的內實體容 (content)完全地對齊,我們則需要針對 pivot 一併修改(預設是 true),就像 Unity 內建控制器提供的功能一樣:

public static class FDRectTransform { static void SetAlignment (this RectTransform rect, Vector2 value, bool adjustPivot) { rect.anchorMax = value; rect.anchorMin = value; if (adjustPivot) rect.pivot = value; } public static void TopLeft (this RectTransform rect, bool adjustPivot = true) { rect.SetAlignment (Vector2.up, adjustPivot); } public static void Top (this RectTransform rect, bool adjustPivot = true) { rect.SetAlignment (new Vector2 (0.5f, 1f), adjustPivot); } public static void TopRight (this RectTransform rect, bool adjustPivot = true) { rect.SetAlignment (Vector2.one, adjustPivot); } public static void Left (this RectTransform rect, bool adjustPivot = true) { rect.SetAlignment (new Vector2 (0f, 0.5f), adjustPivot); } public static void Center (this RectTransform rect, bool adjustPivot = true) { rect.SetAlignment (new Vector2 (0.5f, 0.5f), adjustPivot); } public static void Right (this RectTransform rect, bool adjustPivot = true) { rect.SetAlignment (new Vector2 (1f, 0.5f), adjustPivot); } public static void BottomLeft (this RectTransform rect, bool adjustPivot = true) { rect.SetAlignment (Vector2.zero, adjustPivot); } public static void Bottom (this RectTransform rect, bool adjustPivot = true) { rect.SetAlignment (new Vector2 (0.5f, 0f), adjustPivot); } public static void BottomRight (this RectTransform rect, bool adjustPivot = true) { rect.SetAlignment (Vector2.right, adjustPivot); } public static void Align (this RectTransform rect, FDRectAlignment alignment, bool adjustPivot = true) { switch (alignment) { case FDRectAlignment.TopLeft: rect.TopLeft (adjustPivot); break; case FDRectAlignment.Top: rect.Top (adjustPivot); break; case FDRectAlignment.TopRight: rect.TopRight (adjustPivot); break; case FDRectAlignment.Left: rect.Left (adjustPivot); break; case FDRectAlignment.Center: rect.Center (adjustPivot); break; case FDRectAlignment.Right: rect.Right (adjustPivot); break; case FDRectAlignment.BottomLeft: rect.BottomLeft (adjustPivot); break; case FDRectAlignment.Bottom: rect.Bottom (adjustPivot); break; case FDRectAlignment.BottomRight: rect.BottomRight (adjustPivot); break; } } }

為了使方法更好利用,我們可以再另外實作一組加上位移(offset)以及尺寸(size)設定的方法:

public static void TopLeft (this RectTransform rect, bool adjustPivot, Vector2 offset, Vector2 size) { rect.TopLeft (adjustPivot); rect.anchoredPosition = offset; rect.sizeDelta = size; } public static void Top (this RectTransform rect, bool adjustPivot, Vector2 offset, Vector2 size) { rect.Top (adjustPivot); rect.anchoredPosition = offset; rect.sizeDelta = size; } public static void TopRight (this RectTransform rect, bool adjustPivot, Vector2 offset, Vector2 size) { rect.TopRight (adjustPivot); rect.anchoredPosition = offset; rect.sizeDelta = size; } public static void Left (this RectTransform rect, bool adjustPivot, Vector2 offset, Vector2 size) { rect.Left (adjustPivot); rect.anchoredPosition = offset; rect.sizeDelta = size; } public static void Center (this RectTransform rect, bool adjustPivot, Vector2 offset, Vector2 size) { rect.Center (adjustPivot); rect.anchoredPosition = offset; rect.sizeDelta = size; } public static void Right (this RectTransform rect, bool adjustPivot, Vector2 offset, Vector2 size) { rect.Right (adjustPivot); rect.anchoredPosition = offset; rect.sizeDelta = size; } public static void BottomLeft (this RectTransform rect, bool adjustPivot, Vector2 offset, Vector2 size) { rect.BottomLeft (adjustPivot); rect.anchoredPosition = offset; rect.sizeDelta = size; } public static void Bottom (this RectTransform rect, bool adjustPivot, Vector2 offset, Vector2 size) { rect.Bottom (adjustPivot); rect.anchoredPosition = offset; rect.sizeDelta = size; } public static void BottomRight (this RectTransform rect, bool adjustPivot, Vector2 offset, Vector2 size) { rect.BottomRight (adjustPivot); rect.anchoredPosition = offset; rect.sizeDelta = size; } public static void Align (this RectTransform rect, FDRectAlignment alignment, bool adjustPivot, Vector2 offset, Vector2 size) { switch (alignment) { case FDRectAlignment.TopLeft: rect.TopLeft (adjustPivot, offset, size); break; case FDRectAlignment.Top: rect.Top (adjustPivot, offset, size); break; case FDRectAlignment.TopRight: rect.TopRight (adjustPivot, offset, size); break; case FDRectAlignment.Left: rect.Left (adjustPivot, offset, size); break; case FDRectAlignment.Center: rect.Center (adjustPivot, offset, size); break; case FDRectAlignment.Right: rect.Right (adjustPivot, offset, size); break; case FDRectAlignment.BottomLeft: rect.BottomLeft (adjustPivot, offset, size); break; case FDRectAlignment.Bottom: rect.Bottom (adjustPivot, offset, size); break; case FDRectAlignment.BottomRight: rect.BottomRight (adjustPivot, offset, size); break; } } } 實作延展排版功能

為了方便設定成延展排版模式,我們一樣先定義延展的列舉,總共有七種延展的模式:

HorizontalTop: 水平延展 & 垂直頂置對齊 HorizontalCenter: 水平延展 & 垂直置中對齊 HorizontalBottom: 水平延展 & 垂直底置對齊 VerticalLeft: 垂直延展 & 水平置左對齊 VerticalCenter: 垂直延展 & 水平置中對齊 VerticalRight: 垂直延展 & 水平置右對齊 FullStretch: 水平垂直全延展 public enum FDRectStretch { HorizontalTop, HorizontalCenter, HorizontalBottom, VerticalLeft, VerticalCenter, VerticalRight, FullStretch }

由第二節我們所知道的,當延展的方向不同時,我們則需要不同的幾何參數來決定正確設定這個 RectTransform:

當屬於水平延展時,我們需要的參數是 left、offsetY (posY)、right 以及 height 如 SetStretchHorizontalRect 方法:(此處函式參數名稱使用 padding 前綴自來強調排版的行為)

static void SetStretchHorizontalRect (this RectTransform rect, float paddingLeft, float offsetY, float paddingRight, float height) { rect.offsetMin = new Vector2 (paddingLeft, rect.offsetMin.y); rect.offsetMax = new Vector2 (-paddingRight, rect.offsetMax.y); rect.anchoredPosition = new Vector2 (rect.anchoredPosition.x, offsetY); rect.sizeDelta = new Vector2 (rect.sizeDelta.x, height); }

如果屬於垂直延展時,我們需要的參數則是 offsetX (posX)、top、width 以及 bottom:

static void SetStretchVerticalRect (this RectTransform rect, float offsetX, float paddingTop, float width, float paddingBottom) { rect.offsetMin = new Vector2 (rect.offsetMin.x, paddingBottom); rect.offsetMax = new Vector2 (rect.offsetMax.y, -paddingTop); rect.anchoredPosition = new Vector2 (offsetX, rect.anchoredPosition.y); rect.sizeDelta = new Vector2 (width, rect.sizeDelta.y); }

注意這裡的設定順序是很重要的,設定 offsetMin 以及 offsetMax 屬性的值會間接改變 anchoredPosition 與 sizeDelta ,所以必須要將這兩個設定放在後面。

接者再把要提供的功能方法實作完成就大功告成了:

StretchHorizontalTop StretchHorizontalCenter StretchHorizontalRight StretchVerticalLeft StretchVerticalCenter StretchVerticalRight StretchFull Stretch public static class FDRectTransform { public static void StretchHorizontalTop (this RectTransform rect, float paddingLeft, float offsetY, float paddingRight, float height, bool adjustPivot) { rect.anchorMin = Vector2.up; rect.anchorMax = Vector2.one; if (adjustPivot) { rect.pivot = new Vector2 (rect.pivot.x, 1f); } rect.SetStretchHorizontalRect (paddingLeft, offsetY, paddingRight, height); } public static void StretchHorizontalCenter (this RectTransform rect, float paddingLeft, float offsetY, float paddingRight, float height, bool adjustPivot) { rect.anchorMin = new Vector2 (0f, 0.5f); rect.anchorMax = new Vector2 (1f, 0.5f); if (adjustPivot) { rect.pivot = new Vector2 (rect.pivot.x, 0.5f); } rect.SetStretchHorizontalRect (paddingLeft, offsetY, paddingRight, height); } public static void StretchHorizontalBottom (this RectTransform rect, float paddingLeft, float offsetY, float paddingRight, float height, bool adjustPivot) { rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.right; if (adjustPivot) { rect.pivot = new Vector2 (rect.pivot.x, 0f); } rect.SetStretchHorizontalRect (paddingLeft, offsetY, paddingRight, height); } public static void StretchVerticalLeft (this RectTransform rect, float offsetX, float paddingTop, float width, float paddingBottom, bool adjustPivot) { rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.up; if (adjustPivot) { rect.pivot = new Vector2 (0f, rect.pivot.y); } rect.SetStretchVerticalRect (offsetX, paddingTop, width, paddingBottom); } public static void StretchVerticalCenter (this RectTransform rect, float offsetX, float paddingTop, float width, float paddingBottom, bool adjustPivot) { rect.anchorMin = new Vector2 (0.5f, 0f); rect.anchorMax = new Vector2 (0.5f, 1f); if (adjustPivot) { rect.pivot = new Vector2 (0.5f, rect.pivot.y); } rect.SetStretchVerticalRect (offsetX, paddingTop, width, paddingBottom); } public static void StretchVerticalRight (this RectTransform rect, float offsetX, float paddingTop, float width, float paddingBottom, bool adjustPivot) { rect.anchorMin = Vector2.right; rect.anchorMax = Vector2.one; if (adjustPivot) { rect.pivot = new Vector2 (1f, rect.pivot.y); } rect.SetStretchVerticalRect (offsetX, paddingTop, width, paddingBottom); } public static void StretchFull (this RectTransform rect, float paddingLeft, float paddingTop, float paddingRight, float paddingBottom) { rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = new Vector2 (paddingLeft, paddingBottom); rect.offsetMax = new Vector2 (-paddingRight, -paddingTop); } public static void Stretch (this RectTransform rect, FDRectStretch stretch, float leftOrOffsetX, float topOrOffsetY, float rightOrWidth, float bottomOrHeight, bool adjustPivot) { switch (stretch) { case FDRectStretch.HorizontalTop: rect.StretchHorizontalTop (leftOrOffsetX, topOrOffsetY, rightOrWidth, bottomOrHeight, adjustPivot); break; case FDRectStretch.HorizontalCenter: rect.StretchHorizontalCenter (leftOrOffsetX, topOrOffsetY, rightOrWidth, bottomOrHeight, adjustPivot); break; case FDRectStretch.HorizontalBottom: rect.StretchHorizontalBottom (leftOrOffsetX, topOrOffsetY, rightOrWidth, bottomOrHeight, adjustPivot); break; case FDRectStretch.VerticalLeft: rect.StretchVerticalLeft (leftOrOffsetX, topOrOffsetY, rightOrWidth, bottomOrHeight, adjustPivot); break; case FDRectStretch.VerticalCenter: rect.StretchVerticalCenter (leftOrOffsetX, topOrOffsetY, rightOrWidth, bottomOrHeight, adjustPivot); break; case FDRectStretch.VerticalRight: rect.StretchVerticalRight (leftOrOffsetX, topOrOffsetY, rightOrWidth, bottomOrHeight, adjustPivot); break; case FDRectStretch.FullStretch: rect.StretchFull (leftOrOffsetX, topOrOffsetY, rightOrWidth, bottomOrHeight); break; } } } 結語

RectTransform 是很方便也很常用的排版類別,但若沒搞清楚其背後的原理,往往在撰寫程式上會一頭霧水,怎麼指定位置最後顯示跟預期的不一樣,是在開始實做 UGUI 介面前需要花時間理解的重要基礎。

參考文獻 Unity - Manual: Rect Transform (https://docs.unity3d.com/Manual/class-RectTransform.html). Image via Chris HE, CC Licensed.


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3